#include "TerrainSceneNode.h"
#include "include/Frustum.h"
#include "include/ICameraSceneNode.h"

namespace ISNW
{
namespace scene
{
	void TerrainSceneNode::render()
	{
		if(scenemanager)
		{
			scenemanager->getdriver()->setmaterial(Material);

			scenemanager->getdriver()->setTransform(video::E_WORLD,core::matrix4f());

			scenemanager->getdriver()->drawtriangles(RenderBuffer.vertex_buffer.pointer(),video::STANDAND_VERTEX,VerticesToRender,RenderBuffer.indices_buffer.pointer(),IndicesToRender/3);
		}
	}

	void TerrainSceneNode::onRegisterSceneNode()
	{
		if(!isVisible || !scenemanager ||!scenemanager->getActiveCamera()) return;

		calculatePatchLOD();

		calculateIndices();

		scenemanager->registernodeforrendering(this,E_RT_TERRAIN);
	}

	bool TerrainSceneNode::loadHeightMap(io::IReadFile* rfile,video::color32 vertexColor,s32 smoothFactor)
	{
		if(isVisible == 0 || scenemanager == 0) return false;

		video::IImage* height_image = scenemanager->getdriver()->getImage(rfile->getfilename());

		HeightMapFile = rfile->getfilename();

		if(height_image == 0) return false;

		TerrainData.Size = height_image->getDimention().width;

		switch (TerrainData.PatchSize)
		{
			case E_PS_5:
				if(TerrainData.MaxLOD > 2)
				{
					TerrainData.MaxLOD = 2;
				}
			break;
			case E_PS_9:
				if(TerrainData.MaxLOD > 3)
				{
					TerrainData.MaxLOD = 3;
				}
			break;
			case E_PS_17:
				if(TerrainData.MaxLOD > 4)
				{
					TerrainData.MaxLOD = 4;
				}
			break;
			case E_PS_33:
				if(TerrainData.MaxLOD > 5)
				{
					TerrainData.MaxLOD = 5;
				}
			break;
			case E_PS_65:
				if(TerrainData.MaxLOD > 6)
				{
					TerrainData.MaxLOD = 6;
				}
			break;
			case E_PS_129:
				if(TerrainData.MaxLOD > 7)
				{
					TerrainData.MaxLOD = 7;
				}
			break;
		}

		MeshBuffer*	pMeshBuffer = new MeshBuffer;
		pMeshBuffer->vertex_buffer.set_used(TerrainData.Size * TerrainData.Size);

		video::vertex vertex;
		vertex.normal.set(0,1,0);
		vertex.color = vertexColor;

		const f32 tcScale = 1.0f / (f32)(TerrainData.Size - 1);

		// read from height map to get vertex data
		VerticesToRender = 0;
		for(u32 i = 0; i < TerrainData.Size; ++i)
		{
			for(u32 j = 0; j < TerrainData.Size; ++j)
			{
				vertex.pos.x = j;
				vertex.pos.y = height_image->getPixel(i,j).getLuminance();
				vertex.pos.z = i;

				vertex.coords.x = j * tcScale;
				vertex.coords.y = i * tcScale;

				pMeshBuffer->vertex_buffer[VerticesToRender++] = vertex;
			}
		}

		height_image->drop();
/*
		for(u32 run = 0; run < smoothFactor; ++run)
		{
			for(u32 i = 2; i < TerrainData.Size * TerrainData.Size - 2; ++i)
			{
				pMeshBuffer->vertex_buffer[i].pos.y = 
					(pMeshBuffer->vertex_buffer[i-2].pos.y + pMeshBuffer->vertex_buffer[i-1].pos.y +
					pMeshBuffer->vertex_buffer[i+1].pos.y + pMeshBuffer->vertex_buffer[i+2].pos.y) / 4.0f;

			}
		}

		for(u32 run = 0; run < smoothFactor; ++run)
		{
			for(u32 i = TerrainData.Size; i < TerrainData.Size * (TerrainData.Size - 1); ++i)
			{
				pMeshBuffer->vertex_buffer[i].pos.y = 
					(pMeshBuffer->vertex_buffer[i - TerrainData.Size].pos.y + 
					pMeshBuffer->vertex_buffer[i + TerrainData.Size].pos.y) / 2.0f;
			}
		}
*/
//		calculateNormals( pMeshBuffer );

		RenderBuffer.vertex_buffer.set_used(VerticesToRender);
		for(u32 i = 0; i < VerticesToRender; ++i)
		{
			RenderBuffer.vertex_buffer[i] = pMeshBuffer->vertex_buffer[i];
 			RenderBuffer.vertex_buffer[i].pos = pMeshBuffer->vertex_buffer[i].pos * TerrainData.Scale;
 			RenderBuffer.vertex_buffer[i].pos += TerrainData.Position * TerrainData.Scale;
		}

		pMeshBuffer->drop();

// 		calculateLodDistanceThreshold();
// 
// 		createPatches();
// 
// 		calculatePatchData();

		RenderBuffer.indices_buffer.set_used((TerrainData.Size-1)*(TerrainData.Size-1)*6);
	
		return true;
	}

	void TerrainSceneNode::calculateNormals(MeshBuffer* pMeshBuffer)
	{
		core::vector3df a,b,c,t;

		for(u32 i = 0; i < TerrainData.Size; ++i)
		{
			for(u32 j = 0; j < TerrainData.Size; ++j)
			{
				core::vector3df normal(0,0,0);
				s32 count = 0;

				// Top left
				if(i > 0 && j > 0)
				{
					a = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j - 1].pos;
					b = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					b -= c;
					a -= c;
					t = b.crossproduct(a);
					normal += t.normalize();

					a = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j - 1].pos;
					b = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j -1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = a.crossproduct(b);
					normal += t.normalize();

					count += 2;
				}

				// Top right
				if(i > 0 && j < TerrainData.Size - 1)
				{
					a = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j].pos;
					b = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j + 1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = b.crossproduct(a);
					normal += t.normalize();

					a = pMeshBuffer->vertex_buffer[(i-1) * TerrainData.Size + j + 1].pos;
					b = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j + 1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = b.crossproduct(a);
					normal += t.normalize();

					count += 2;
				}

				// Bottom left
				if(i < TerrainData.Size - 1 && j > 0)
				{
					a = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j - 1].pos;
					b = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = a.crossproduct(b);
					normal += t.normalize();

					a = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j - 1].pos;
					b = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j - 1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = a.crossproduct(b);
					normal += t.normalize();

					count += 2;
				}

				// Bottom right
				if(i < TerrainData.Size - 1 && j < TerrainData.Size - 1)
				{
					a = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j + 1].pos;
					b = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j + 1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = a.crossproduct(b);
					normal += t.normalize();

					a = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j].pos;
					b = pMeshBuffer->vertex_buffer[(i+1) * TerrainData.Size + j + 1].pos;
					c = pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].pos;

					a -= c;
					b -= c;
					t = a.crossproduct(b);
					normal += t.normalize();

					count += 2;
				}

				if(count > 0)
				{
					normal.normalize();
				}
				else
				{
					normal.set(0,1,0);
				}

				pMeshBuffer->vertex_buffer[i * TerrainData.Size + j].normal = normal;
			}
		}
	}

	void TerrainSceneNode::calculateLodDistanceThreshold()
	{
		if(TerrainData.LODDistanceThreshold)
		{
			delete[] TerrainData.LODDistanceThreshold;
		}

		TerrainData.LODDistanceThreshold = new f64[TerrainData.MaxLOD];

		for(s32 i = 0; i < TerrainData.MaxLOD; ++i)
		{
			TerrainData.LODDistanceThreshold[i] = 
				(TerrainData.PatchSize * TerrainData.PatchSize) * 
				(TerrainData.Scale.x * TerrainData.Scale.z) *
				(i + 1 + i / 2.0f) * (i + 1 + i / 2.0f);
		}
	}

	void TerrainSceneNode::createPatches()
	{
		TerrainData.PatchCount = (TerrainData.Size) / TerrainData.CalcPatchSize;

		if(TerrainData.Patches)
		{
			delete[] TerrainData.Patches;
		}

		TerrainData.Patches = new SPatch[TerrainData.PatchCount * TerrainData.PatchCount];
	}

	void TerrainSceneNode::calculatePatchData()
	{
		TerrainData.BoundingBox.set(999999.9f,999999.9f,999999.9f,-999999.9f,-999999.9f,-999999.9f);

		for(u32 row = 0; row < TerrainData.PatchCount; ++row)
		{
			for(u32 line = 0; line < TerrainData.PatchCount; ++line)
			{
				u32 idx = row * TerrainData.PatchCount + line;
				TerrainData.Patches[idx].CurrentLOD = 0;
				TerrainData.Patches[idx].BoundingBox.set(999999.9f,999999.9f,999999.9f,-999999.9f,-999999.9f,-999999.9f);

				// patch size
				for(u32 i = 0; i < TerrainData.CalcPatchSize; ++i)
				{
					u32 index = row * TerrainData.CalcPatchSize * TerrainData.Size + line * TerrainData.CalcPatchSize + 
								i * TerrainData.Size;

					for(u32 j = 0; j < TerrainData.CalcPatchSize; ++j)
					{
						TerrainData.Patches[idx].BoundingBox.addpoint(
							RenderBuffer.vertex_buffer[index + j].pos);
					}
				}

				TerrainData.Patches[idx].Center = TerrainData.Patches[idx].BoundingBox.getcenter();

				// assign neighbors

				// left
				if(line > 0)
				{
					TerrainData.Patches[idx].Left = &TerrainData.Patches[row*TerrainData.PatchCount + line - 1];
				}
				else
				{
					TerrainData.Patches[idx].Left = 0;
				}

				// right
				if(line < TerrainData.PatchCount - 1)
				{
					TerrainData.Patches[idx].Right = &TerrainData.Patches[row*TerrainData.PatchCount + line + 1];
				}
				else
				{
					TerrainData.Patches[idx].Right = 0;
				}

				// Top
				if(row > 0)
				{
					TerrainData.Patches[idx].Top = &TerrainData.Patches[(row-1)*TerrainData.PatchCount + line];
				}
				else
				{
					TerrainData.Patches[idx].Top = 0;
				}

				// Bottom
				if(row < TerrainData.PatchCount - 1)
				{
					TerrainData.Patches[idx].Bottom = &TerrainData.Patches[(row+1)*TerrainData.PatchCount + line];
				}
				else
				{
					TerrainData.Patches[idx].Bottom = 0;
				}


				TerrainData.BoundingBox.addbox(TerrainData.Patches[idx].BoundingBox);
			}
		}

		TerrainData.Center = TerrainData.BoundingBox.getcenter();
	}

	void TerrainSceneNode::calculatePatchLOD()
	{
		core::vector3df cameraPostion = scenemanager->getActiveCamera()->getAbsolutePosition();

		if(core::_abs_(cameraPostion.x - oldCameraPosition.x) < CameraMovementDelt &&
			core::_abs_(cameraPostion.y - oldCameraPosition.y) < CameraMovementDelt &&
			core::_abs_(cameraPostion.z - oldCameraPosition.z) < CameraMovementDelt)
		{
			return;
		}

		scene::Frustum frustum = scenemanager->getActiveCamera()->getFrustum();

		for(u32 i = 0; i < TerrainData.PatchCount; ++i)
		{
			for(u32 j = 0; j < TerrainData.PatchCount; ++j)
			{
				SPatch& patch = TerrainData.Patches[i * TerrainData.PatchCount + j];
				if(frustum.getBoundBox().isintersectwithbox(patch.BoundingBox))
				{
					core::vector3df patch_center = patch.BoundingBox.getcenter();
					f32 distance = patch_center.getdistancefrom(cameraPostion);
					for(u32 u = 0; u < TerrainData.MaxLOD; ++u)
					{
						if(TerrainData.LODDistanceThreshold[u] >= distance)
						{
							patch.CurrentLOD = u;
							break;
						}

						{
							patch.CurrentLOD = 0;
						}
					}
				}
				else
				{
					patch.CurrentLOD = -1;
				}
			}
		}
	}

	/*
	void TerrainSceneNode::calculateIndices()
	{
		IndicesToRender = 0;

		u32 index11;
		u32 index12;
		u32 index21;
		u32 index22;

		for(u32 i = 0; i < TerrainData.PatchCount; ++i)
		{
			for(u32 j = 0; j < TerrainData.PatchCount; ++j)
			{
				u32 index = i * TerrainData.PatchCount + j;
				if(TerrainData.Patches[index].CurrentLOD >= 0)
				{
					u32 x = 0;
					u32 z = 0;

					u32 step = 1 << TerrainData.Patches[index].CurrentLOD;
					step = 1;

					while(z < TerrainData.CalcPatchSize)
					{
						index11 = getindex(i,j,index,z,x);
						index12 = getindex(i,j,index,z,x + step);
						index21 = getindex(i,j,index,z + step,x);
						index22 = getindex(i,j,index,z + step,x + step);

						RenderBuffer.indices_buffer[IndicesToRender++] = index11;
						RenderBuffer.indices_buffer[IndicesToRender++] = index22;
						RenderBuffer.indices_buffer[IndicesToRender++] = index12;

						RenderBuffer.indices_buffer[IndicesToRender++] = index11;
						RenderBuffer.indices_buffer[IndicesToRender++] = index21;
						RenderBuffer.indices_buffer[IndicesToRender++] = index22;

						x += step;

						if(x >= TerrainData.CalcPatchSize)
						{
							x = 0; 
							z += step;
						}
					}
				}
			}
		}
	}
	*/

	void TerrainSceneNode::calculateIndices()
	{
		IndicesToRender = 0;
		for(u32 z = 0; z < TerrainData.Size - 1; ++z)
		{
			for(u32 x = 0; x < TerrainData.Size - 1; ++x)
			{
				u32 index11 = z * TerrainData.Size + x;
				u32 index12 = z * TerrainData.Size + x + 1;
				u32 index21 = (z+1) * TerrainData.Size + x;
				u32 index22 = (z+1) * TerrainData.Size + x + 1;

				RenderBuffer.indices_buffer[IndicesToRender++] = index11;
				RenderBuffer.indices_buffer[IndicesToRender++] = index21;
				RenderBuffer.indices_buffer[IndicesToRender++] = index22;

				RenderBuffer.indices_buffer[IndicesToRender++] = index11;
				RenderBuffer.indices_buffer[IndicesToRender++] = index22;
				RenderBuffer.indices_buffer[IndicesToRender++] = index12;
			}
		}
	}

	s32 TerrainSceneNode::getindex(u32 patchi,u32 patchj,u32 idx,u32 v,u32 h)
	{
		/*
		// Top
		if(v == 0)
		{
			if(TerrainData.Patches[idx].Top &&
				TerrainData.Patches[idx].CurrentLOD < TerrainData.Patches[idx].Top->CurrentLOD &&
				h % (1 << TerrainData.Patches[idx].Top->CurrentLOD) != 0)
			{
				h -= h % (1 << TerrainData.Patches[idx].Top->CurrentLOD);
			}
		}

		// Bottom
		if(v == TerrainData.CalcPatchSize)
		{
			if(TerrainData.Patches[idx].Bottom &&
				TerrainData.Patches[idx].CurrentLOD < TerrainData.Patches[idx].Bottom->CurrentLOD &&
				h % (1 << TerrainData.Patches[idx].Bottom->CurrentLOD) != 0)
			{
				h -= h % (1 << TerrainData.Patches[idx].Bottom->CurrentLOD);
			}
		}

		// Left
		if(h == 0)
		{
			if(TerrainData.Patches[idx].Left &&
				TerrainData.Patches[idx].CurrentLOD < TerrainData.Patches[idx].Left->CurrentLOD &&
				v % (1 << TerrainData.Patches[idx].Left->CurrentLOD) != 0)
			{
				v -= v % (1 << TerrainData.Patches[idx].Left->CurrentLOD);
			}
		}

		// Right
		if(h == TerrainData.CalcPatchSize)
		{
			if(TerrainData.Patches[idx].Right &&
				TerrainData.Patches[idx].CurrentLOD < TerrainData.Patches[idx].Right->CurrentLOD &&
				v % (1 << TerrainData.Patches[idx].Right->CurrentLOD) != 0)
			{
				v -= v % (1 << TerrainData.Patches[idx].Right->CurrentLOD);
			}
		}
*/
		return ((patchi * TerrainData.CalcPatchSize + v) * TerrainData.Size) + 
				(patchj * TerrainData.CalcPatchSize + h);
	}

} // end namespace scene
} // end namespace ISNW
